﻿/*
* Music - Generate wave file according to the code file(s) given.
* (c) apple-ba 2023-2024. All rights reserved.
*/

//Includes.
#include <cmath>
#include <fstream>
#include <iostream>
#include <sstream>
#include <string>
#include <tchar.h>
#include <Windows.h>

//Libraries.
#pragma comment(lib, "winmm.lib")
#pragma comment(lib, "user32.lib")

using namespace std;

//Constant: PI.
#define M_PI 3.14159265358979323846

//Struct: WavFileHeader.
struct WavFileHeader
{
	FOURCC RiffHdr;				//"RIFF"
	uint32_t ChunkSize;			//file size - 8
	FOURCC WavHdr;				//"WAVE"
	FOURCC FmtHdr;				//"fmt "
	uint32_t HdrLen;			//16, length of above
	uint16_t DataType;			//1->PCM
	uint16_t ChannelNumber;		//2 Channels
	uint32_t SampleRate;		//44100 Hz
	uint32_t SamplePerSec;		//8000 sample per second
	uint16_t BytePerSample;		//Bytes per sample
	uint16_t BitsPerSample;		//Bits per sample
	FOURCC dataHdr;				//"data"
	uint32_t RawSize;			//data size from this point
};

//Variable: Wave File Header.
WavFileHeader  wavHdr;

//Constant: Frequencies.
const double FREQ[] =
{
	 261.625565300598634677849993523304701,
	 277.182630976872096248786333601210237,
	 293.664767917407560262783013860579610,
	 311.126983722080910736371519326133577,
	 329.627556912869929735841761046555075,
	 349.228231433003884445375240639907817,
	 369.994422711634398933695209542614553,
	 391.995435981749294085699530459825503,
	 415.304697579945138522441788933715125,
	 440.000000000000000000000000000000000,
	 466.163761518089916407203129776390348,
	 493.883301256124111830754541858838987,
	 523.251130601197269355699987046609402,
	 554.365261953744192497572667202420474,
	 587.329535834815120525566027721159220,
	 622.253967444161821472743038652267154,
	 659.255113825739859471683522093110150,
	 698.456462866007768890750481279815634,
	 739.988845423268797867390419085229107,
	 783.990871963498588171399060919651007,
	 830.609395159890277044883577867430250,
	 880.000000000000000000000000000000000,
	 932.327523036179832814406259552780696,
	 987.766602512248223661509083717677974,
	1046.502261202394538711399974093218805,
	1108.730523907488384995145334404840948,
	1174.659071669630241051132055442318441,
	1244.507934888323642945486077304534309,
	1318.510227651479718943367044186220300,
	1396.912925732015537781500962559631269,
	1479.977690846537595734780838170458215,
	1567.981743926997176342798121839302014,
	1661.218790319780554089767155734860501,
	1760.000000000000000000000000000000000,
	1864.655046072359665628812519105561393,
	1975.533205024496447323018167435355948
};

//Set output text color
bool NoColor = false;

void SetColor(WORD foreground = 7)
{
	if (NoColor)
		return;
	HANDLE handle_out = GetStdHandle(STD_OUTPUT_HANDLE);
	CONSOLE_SCREEN_BUFFER_INFO csbi;
	GetConsoleScreenBufferInfo(handle_out, &csbi);
	SetConsoleTextAttribute(handle_out, foreground);
}

#define GREEN FOREGROUND_GREEN	
#define BLUE FOREGROUND_BLUE	
#define RED FOREGROUND_RED	

//Constant: Version.
#ifdef _DEBUG
#ifdef _M_AMD64
#define Version (string("X64 DEBUG ") + __DATE__ + " " + __TIME__).c_str()
#endif // _M_AMD64
#ifdef _M_IX86_FP
#define Version (string("X86 DEBUG ") + __DATE__ + " " + __TIME__).c_str()
#endif // _M_IX86_FP
#else
#ifdef _M_AMD64
#define Version (string("X64 RELEASE ") + __DATE__).c_str()
#endif // _M_AMD64
#ifdef _M_IX86_FP
#define Version (string("X86 RELEASE ") + __DATE__).c_str()
#endif // _M_IX86_FP
#endif // _DEBUG

//Constant: Lowest and Highest code file version supported.
#define LowestSupported 1
#define HighestSupported 1

//Displays help information and exit.
void help()
{
	printf_s("Generate wave file according to the code file(s) given.\n");
	printf_s("\n");
	printf_s("usage: music [options] file1 [file2] [file3] ...\n");
	printf_s("\n");
	printf_s("[options]: \n");
	printf_s("  -c     Do not use colorful output.\n");
	printf_s("  -h     Displays this help.\n");
	printf_s("  -?     Displays this help.\n");
	printf_s("file1, [file2], [file3], ...: code file(s), ended with \".txt\".\n");
	printf_s("\n");
	printf_s("Press any key to continue...");
	system("pause >nul");
	printf_s("\n");
	exit(0);

}

//The main function.
int main(int argc, char** argv)
{
	//Displays name and version.
	printf_s("Music, version: %s.\n", Version);
	printf_s("apple-ba (c) 2023-2024.\n\n");
	printf_s("Lowest code file version supported: %d,\n", LowestSupported);
	printf_s("Highest code file version supported: %d.\n\n", HighestSupported);

	//Without arguments: Display help.
	if (argc == 1)
	{
		help();
	}

	//Counters for results.
	int Success = 0, Failure = 0;

	//Process input files one by one.
	for (int ic = 1; ic < argc; ic++)
	{
		//Options.
		if (argv[ic][0] == '-' || argv[ic][0] == '/')
		{
			switch (argv[ic][1])
			{
			case 'c':
				NoColor = true;
				break;
			case '?':
			case 'h':
				help();
				break;
			default:
				break;
			}
			continue;
		}

		FILE* fp = NULL;

		// Call "goto ContinueFor" will stop processing the present file and continue to the next.
		string sa;
		goto EndOfContinueFor;
	ContinueFor:
		if (fp != NULL)fclose(fp);
		system(("del \"" + string(argv[ic]).substr(0, string(argv[ic]).size() - 3) + "wav\"").c_str());
		SetColor(RED);
		cout << "Failure." << endl;
		SetColor();
		Failure++;
		continue;
	EndOfContinueFor:

		//Displays the number and input file name.
		cout << "=== Input #" << ic << ": " << argv[ic] << " ===" << endl;
		
		//Check the input file name.
		if (string(argv[ic]).size() < 4 || string(argv[ic])[(string(argv[ic])).size() - 4] != '.')
		{
			SetColor(RED);
			cout << "SYSTEM ERROR: Illegal file name." << endl;
			SetColor();
			goto ContinueFor;
		}

		//Try to open the input file.
		ifstream in;
		while (true)
		{
			in.open(argv[ic]);
			if (!in.is_open())
			{
				char errstr[1024];
				strerror_s(errstr, errno);
				SetColor(RED);
				printf_s("SYSTEM ERROR: Cannot open input file %s: %s\n", argv[ic], errstr);
				SetColor(GREEN | BLUE);
				printf_s("Retry? [Y/N]");
				SetColor();
				char c;
				cin >> c;
				if (c == 'Y' || c == 'y')continue;
				else if (c == 'N' || c == 'n')goto ContinueFor;

			}
			else break;
		}

		//String inputed.
		string s;

		//Check code file version.
		bool good = false;
		int ver = -1;
		while (getline(in, s))
		{
			if (s.find("Music code file Version ") != s.npos)
			{
				stringstream ss;
				ss << s;
				ss >> s >> s >> s >> s;
				ss >> ver;
				if (ver >= LowestSupported && ver <= HighestSupported)
				{
					good = true;
				}
				break;
			}
		}
		if (!good)
		{
			SetColor(RED);
			printf_s("INPUT FILE ERROR: Unsupported code file.\n");
			if (ver != -1)
			{
				printf_s("Input file version: %d\n", ver);
				SetColor();
				printf_s("Lowest code file version supported: %d,\n", LowestSupported);
				printf_s("Highest code file version supported: %d.\n", HighestSupported);
			}
			else printf_s("Input file version unknown, maybe your code file is too old.\n");
			SetColor();
			goto ContinueFor;

		}

		//Try to open the output file.
		while (true)
		{
			errno_t err = fopen_s(&fp, (string(argv[ic]).substr(0, string(argv[ic]).size() - 3) + "wav").c_str(), "wb");
			if (err)
			{
				char errstr[1024];
				strerror_s(errstr, errno);
				SetColor(RED);
				printf_s( "SYSTEM ERROR: Cannot open output file %s: %s\n", (string(argv[ic]).substr(0, string(argv[ic]).size() - 3) + "wav").c_str(), errstr);
				SetColor(GREEN | BLUE);
				printf_s("Retry? [Y/N]");
				SetColor();
				char c;
				cin >> c;
				if (c == 'Y' || c == 'y')continue;
				else if (c == 'N' || c == 'n') goto ContinueFor;
			}
			else break;
		}

		
		//Calculating the length of the music.
		double msec = 0;
		double p = 750; //v=80
		printf_s("Calculating the length of the music. Please wait...");
		in.clear();
		in.seekg(0, ios::beg);
		while (getline(in, s))
		{
			if (s[0] == ';')
			{
				continue;
			}
			if (s.find("Music code file Version ") != s.npos)
			{
				continue;
			}
			while (s.size() != 0)
			{
				string ss;
				size_t fspace = s.find_first_of(' '), fslide = s.find_first_of('~');
				if (fspace == s.npos && fslide == s.npos)
				{
					ss = s;
					s = "";
				}
				else if (fspace == s.npos)
				{
					ss = s.substr(0, fslide);
					s.erase(0, fslide);
					s.erase(0, s.find_first_not_of('~'));
				}
				else if (fslide == s.npos)
				{
					ss = s.substr(0, fspace);
					s.erase(0, fspace);
					s.erase(0, s.find_first_not_of(' '));
				}
				else
				{
					if (fspace < fslide)
					{
						ss = s.substr(0, fspace);
						s.erase(0, fspace);
						s.erase(0, s.find_first_not_of(' '));
					}
					else
					{
						ss = s.substr(0, fslide);
						s.erase(0, fslide);
						s.erase(0, s.find_first_not_of('~'));
					}
				}

				if (ss.find("1=") != ss.npos||s[0] == '(' || s[0] == ')')
				{}
				else if (ss.find("v=") != ss.npos)
					p = 60000.0 / atof(ss.substr(2, ss.size() - 2).c_str());
				else
				{
					if (ss[0] != 's')
					{
						int i;
						for (i = 0; i < ss.size(); i++)
							if (ss[i] >= '0' && ss[i] <= '9')
								break;
						ss.erase(0, size_t(i) + 1);
					}
					else
						ss.erase(0, 1);
					double t = 0;
					if (ss.size() == 0)
						t = 1;
					else
						for (int i = 0; i < ss.size(); i++)
						{
							if (ss[i] >= '0' && ss[i] <= '9')
								t += ss[i] - '0';
							else if (ss[i] == 'h')
								t += 0.5;
							else if (ss[i] == 'q')
								t += 0.25;
							else if (ss[i] == 'e')
								t += 0.125;
						}

					t *= p;
					t = ceil(t);
					msec += t;
				}
			}
		}
		SetColor(GREEN);
		printf_s("Done.\n");
		SetColor();
		
		//Write the wave file header.
		ZeroMemory(&wavHdr, sizeof(wavHdr));
		wavHdr.RiffHdr = FOURCC_RIFF;
		wavHdr.ChunkSize = sizeof(wavHdr) - 8 + uint32_t(ceil(88.2 * msec));
		wavHdr.WavHdr = mmioFOURCC('W', 'A', 'V', 'E');
		wavHdr.FmtHdr = mmioFOURCC('f', 'm', 't', ' ');
		wavHdr.HdrLen = 16;
		wavHdr.DataType = WAVE_FORMAT_PCM;
		wavHdr.ChannelNumber = 2;
		wavHdr.SampleRate = 22050;
		wavHdr.SamplePerSec = 22050 * 4;
		wavHdr.BytePerSample = 4;
		wavHdr.BitsPerSample = 16;
		wavHdr.dataHdr = mmioFOURCC('d', 'a', 't', 'a');
		wavHdr.RawSize = uint32_t(ceil(4 * (22050 * msec / 1000)));

		fwrite(&wavHdr, sizeof(wavHdr), 1, fp);

		//Reset the input file stream.
		in.clear();
		in.seekg(0, ios::beg);

		//Write to wave file.
		uint16_t wave[2]{};
		int C = 12; // 1=C
		p = 750; //v=80
		bool quiet = false;
		int cmsec = 0;
		printf_s("Generating Wave File. Please wait.\n");
		int LineNumber = 0;
		while (getline(in, s))
		{
			LineNumber++;
			if (s[0] == ';')
			{
				continue;
			}
			if (s.find("Music code file Version ") != s.npos)
			{
				continue;
			}
			while (s.size() != 0)
			{
				printf_s("\rProgress: %2d%% ", int(cmsec * 100.0 / msec));
				bool link = false;
				string ss;
				size_t fspace = s.find_first_of(' '), fslide = s.find_first_of('~');
				if (fspace == s.npos && fslide == s.npos)
				{
					ss = s;
					s = "";
				}
				else if (fspace == s.npos)
				{
					ss = s.substr(0, fslide);
					s.erase(0, fslide);
					s.erase(0, s.find_first_not_of('~'));
					link = true;
				}
				else if (fslide == s.npos)
				{
					ss = s.substr(0, fspace);
					s.erase(0, fspace);
					s.erase(0, s.find_first_not_of(' '));
				}
				else
				{
					if (fspace < fslide)
					{
						ss = s.substr(0, fspace);
						s.erase(0, fspace);
						s.erase(0, s.find_first_not_of(' '));
					}
					else
					{
						ss = s.substr(0, fslide);
						s.erase(0, fslide);
						s.erase(0, s.find_first_not_of('~'));
						link = true;
					}

				}


				if (ss == "(")
				{
					quiet = true;
				}
				else if (ss == ")")
				{
					quiet = false;
				}
				else if (ss.find("1=") != ss.npos)
				{
					switch (ss[2] >= 'a' ? ss[2] - 'a' + 'A' : ss[2])
					{
					case 'C':
						C = 12;
						break;
					case 'D':
						C = 14;
						break;
					case 'E':
						C = 16;
						break;
					case 'F':
						C = 17;
						break;
					case 'G':
						C = 19;
						break;
					case 'A':
						C = 9;
						break;
					case 'B':
						C = 11;
						break;
					case 'U':
						switch (ss[3] >= 'a' ? ss[3] - 'a' + 'A' : ss[3])
						{
						case 'C':
							C = 13;
							break;
						case 'D':
							C = 15;
							break;
						case 'F':
							C = 18;
							break;
						case 'G':
							C = 20;
							break;
						case 'A':
							C = 10;
							break;
						default:
							SetColor(RED);
							cout << "INPUT FILE ERROR:" << endl;
							cout << "Line " << LineNumber << ": Illegal character." << endl;
							SetColor();
							goto ContinueFor;
						}
						break;
					default:
						SetColor(RED);
						cerr << "INPUT FILE ERROR:" << endl;
						cerr << "Line " << LineNumber << ": Illegal character." << endl;
						SetColor();
						goto ContinueFor;
					}
				}
				else if (ss.find("v=") != ss.npos)
				{
					p = 60000.0 / atof(ss.substr(2, ss.size() - 2).c_str());
				}

				else
				{
					double freq = 0;
					if (ss[0] != 's')
					{
						int i, h = 0;
						bool u = false;
						for (i = 0; i < ss.size(); i++)
						{
							if (ss[i] >= '0' && ss[i] <= '9')
								break;
							else if (ss[i] == 'h')
								h++;
							else if (ss[i] == 'l')
								h--;
							else if (ss[i] == 'u')
								u = true;
							else
							{
								SetColor(RED);
								cout << "\rINPUT FILE ERROR:" << endl;
								cout << "Line " << LineNumber << ": Illegal character." << endl;
								SetColor();
								goto ContinueFor;
							}
						}

						if (ss[i] == '1')
							if (!u)freq = FREQ[C];
							else freq = FREQ[C + 1];
						else if (ss[i] == '2')
							if (!u)freq = FREQ[C + 2];
							else freq = FREQ[C + 3];
						else if (ss[i] == '3')
							freq = FREQ[C + 4];
						else if (ss[i] == '4')
							if (!u)freq = FREQ[C + 5];
							else freq = FREQ[C + 6];
						else if (ss[i] == '5')
							if (!u)freq = FREQ[C + 7];
							else freq = FREQ[C + 8];
						else if (ss[i] == '6')
							if (!u)freq = FREQ[C + 9];
							else freq = FREQ[C + 10];
						else freq = FREQ[C + 11];

						if (h > 0)freq *= pow(2, h);
						else freq /= pow(2, -h);

						freq = ceil(freq);

						ss.erase(0, size_t(i) + 1);
					}
					else
						ss.erase(0, 1);
					double t = 0;
					if (ss.size() == 0)
						t = 1;
					else
						for (int i = 0; i < ss.size(); i++)
						{
							if (isdigit(ss[i]))
							{
								t += ss[i] - '0';
							}
							else if (ss[i] == 'h')
								t += 0.5;
							else if (ss[i] == 'q')
								t += 0.25;
							else if (ss[i] == 'e')
								t += 0.125;
						}

					t *= p;
					t = ceil(t);
					if (link)
					{
						for (int i = 0; i < 22050 * (t - p / 16) / 1000; ++i)
						{
							//Pure Sine Wave
							wave[0] = uint16_t(freq != 0 ? (INT16_MAX) * (quiet ? 0.08 : 1) * sin(2 * M_PI * freq * i / 22050) : 0);
							wave[1] = wave[0];
							fwrite((const void*)&wave, sizeof(wave[0]), 2, fp);
						}
						for (int i = 0; i < 22050 * p / 16 / 1000; ++i)
						{
							//Keep quiet
							wave[0] = 0;
							wave[1] = wave[0];
							fwrite((const void*)&wave, sizeof(wave[0]), 2, fp);
						}

					}
					else
					{
						for (int i = 0; i < 22050 * (t - p / 8) / 1000; ++i)
						{
							//Pure Sine Wave
							wave[0] = uint16_t(freq != 0 ? (INT16_MAX) * (quiet ? 0.08 : 1) * sin(2 * M_PI * freq * i / 22050) : 0);
							wave[1] = wave[0];
							fwrite((const void*)&wave, sizeof(wave[0]), 2, fp);
						}
						for (int i = 0; i < 22050 * p / 8 / 1000; ++i)
						{
							//Keep quiet
							wave[0] = 0;
							wave[1] = wave[0];
							fwrite((const void*)&wave, sizeof(wave[0]), 2, fp);
						}
					}
					cmsec += int(t);
				}
			}
		}
		fclose(fp);
		SetColor(GREEN);
		printf_s("\rDone.          \n\n");
		SetColor();
		Success++;
	}
	
	//Display results and exit.
	printf_s("=== All works have been done. ===\n");
	printf_s("Counts:");
	if (Success != 0)SetColor(GREEN);
	printf_s("%d Success", Success);
	SetColor();
	printf_s(", ");
	if (Failure != 0)SetColor(RED);
	printf_s("%d Failure\n", Failure);
	SetColor();
	printf_s("Press any key to continue...");
	system("pause > nul");
	printf_s("\n");

	if (Failure != 0)
		return 1;
	else
		return 0;
}

